/*
 * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/HttpConnection.java,v 1.107 2005/01/14 21:30:59 olegk Exp $
 * $Revision: 480424 $
 * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
 *
 * ====================================================================
 *
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 */

package org.apache.commons.httpclient;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;

import org.apache.commons.httpclient.params.HttpConnectionParams;
import org.apache.commons.httpclient.protocol.Protocol;
import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory;
import org.apache.commons.httpclient.util.EncodingUtil;
import org.apache.commons.httpclient.util.ExceptionUtil;
import org.apache.commons.logging.Log;

/**
 * An abstraction of an HTTP {@link InputStream} and {@link OutputStream} pair,
 * together with the relevant attributes.
 * <p>
 * The following options are set on the socket before getting the input/output
 * streams in the {@link #open()} method:
 * <table border=1>
 * <tr>
 * <th>Socket Method
 * <th>Sockets Option
 * <th>Configuration
 * </tr>
 * <tr>
 * <td>{@link java.net.Socket#setTcpNoDelay(boolean)}
 * <td>SO_NODELAY
 * <td>{@link HttpConnectionParams#setTcpNoDelay(boolean)}
 * </tr>
 * <tr>
 * <td>{@link java.net.Socket#setSoTimeout(int)}
 * <td>SO_TIMEOUT
 * <td>{@link HttpConnectionParams#setSoTimeout(int)}
 * </tr>
 * <tr>
 * <td>{@link java.net.Socket#setSendBufferSize(int)}
 * <td>SO_SNDBUF
 * <td>{@link HttpConnectionParams#setSendBufferSize(int)}
 * </tr>
 * <tr>
 * <td>{@link java.net.Socket#setReceiveBufferSize(int)}
 * <td>SO_RCVBUF
 * <td>{@link HttpConnectionParams#setReceiveBufferSize(int)}
 * </tr>
 * </table>
 * 
 * @author Rod Waldhoff
 * @author Sean C. Sullivan
 * @author Ortwin Glueck
 * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
 * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
 * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
 * @author Michael Becke
 * @author Eric E Johnson
 * @author Laura Werner
 * 
 * @version $Revision: 480424 $ $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov
 *          2006) $
 */
public class HttpConnection {

	// ----------------------------------------------------------- Constructors

	/**
	 * Creates a new HTTP connection for the given host and port.
	 * 
	 * @param host
	 *            the host to connect to
	 * @param port
	 *            the port to connect to
	 */
	public HttpConnection(String host, int port) {
		this(null, -1, host, null, port, Protocol.getProtocol("http"));
	}

	/**
	 * Creates a new HTTP connection for the given host and port using the given
	 * protocol.
	 * 
	 * @param host
	 *            the host to connect to
	 * @param port
	 *            the port to connect to
	 * @param protocol
	 *            the protocol to use
	 */
	public HttpConnection(String host, int port, Protocol protocol) {
		this(null, -1, host, null, port, protocol);
	}

	/**
	 * Creates a new HTTP connection for the given host with the virtual alias
	 * and port using given protocol.
	 * 
	 * @param host
	 *            the host to connect to
	 * @param virtualHost
	 *            the virtual host requests will be sent to
	 * @param port
	 *            the port to connect to
	 * @param protocol
	 *            the protocol to use
	 */
	public HttpConnection(String host, String virtualHost, int port,
			Protocol protocol) {
		this(null, -1, host, virtualHost, port, protocol);
	}

	/**
	 * Creates a new HTTP connection for the given host and port via the given
	 * proxy host and port using the default protocol.
	 * 
	 * @param proxyHost
	 *            the host to proxy via
	 * @param proxyPort
	 *            the port to proxy via
	 * @param host
	 *            the host to connect to
	 * @param port
	 *            the port to connect to
	 */
	public HttpConnection(String proxyHost, int proxyPort, String host, int port) {
		this(proxyHost, proxyPort, host, null, port, Protocol
				.getProtocol("http"));
	}

	/**
	 * Creates a new HTTP connection for the given host configuration.
	 * 
	 * @param hostConfiguration
	 *            the host/proxy/protocol to use
	 */
	public HttpConnection(HostConfiguration hostConfiguration) {
		this(hostConfiguration.getProxyHost(),
				hostConfiguration.getProxyPort(), hostConfiguration.getHost(),
				hostConfiguration.getPort(), hostConfiguration.getProtocol());
		this.localAddress = hostConfiguration.getLocalAddress();
	}

	/**
	 * Creates a new HTTP connection for the given host with the virtual alias
	 * and port via the given proxy host and port using the given protocol.
	 * 
	 * @param proxyHost
	 *            the host to proxy via
	 * @param proxyPort
	 *            the port to proxy via
	 * @param host
	 *            the host to connect to. Parameter value must be non-null.
	 * @param virtualHost
	 *            No longer applicable.
	 * @param port
	 *            the port to connect to
	 * @param protocol
	 *            The protocol to use. Parameter value must be non-null.
	 * 
	 * @deprecated use #HttpConnection(String, int, String, int, Protocol)
	 */
	public HttpConnection(String proxyHost, int proxyPort, String host,
			String virtualHost, int port, Protocol protocol) {
		this(proxyHost, proxyPort, host, port, protocol);
	}

	/**
	 * Creates a new HTTP connection for the given host with the virtual alias
	 * and port via the given proxy host and port using the given protocol.
	 * 
	 * @param proxyHost
	 *            the host to proxy via
	 * @param proxyPort
	 *            the port to proxy via
	 * @param host
	 *            the host to connect to. Parameter value must be non-null.
	 * @param port
	 *            the port to connect to
	 * @param protocol
	 *            The protocol to use. Parameter value must be non-null.
	 */
	public HttpConnection(String proxyHost, int proxyPort, String host,
			int port, Protocol protocol) {

		if (host == null) {
			throw new IllegalArgumentException("host parameter is null");
		}
		if (protocol == null) {
			throw new IllegalArgumentException("protocol is null");
		}

		proxyHostName = proxyHost;
		proxyPortNumber = proxyPort;
		hostName = host;
		portNumber = protocol.resolvePort(port);
		protocolInUse = protocol;
	}

	// ------------------------------------------ Attribute Setters and Getters

	/**
	 * Returns the connection socket.
	 * 
	 * @return the socket.
	 * 
	 * @since 3.0
	 */
	protected Socket getSocket() {
		return this.socket;
	}

	/**
	 * Returns the host.
	 * 
	 * @return the host.
	 */
	public String getHost() {
		return hostName;
	}

	/**
	 * Sets the host to connect to.
	 * 
	 * @param host
	 *            the host to connect to. Parameter value must be non-null.
	 * @throws IllegalStateException
	 *             if the connection is already open
	 */
	public void setHost(String host) throws IllegalStateException {
		if (host == null) {
			throw new IllegalArgumentException("host parameter is null");
		}
		assertNotOpen();
		hostName = host;
	}

	/**
	 * Returns the target virtual host.
	 * 
	 * @return the virtual host.
	 * 
	 * @deprecated no longer applicable
	 */

	public String getVirtualHost() {
		return this.hostName;
	}

	/**
	 * Sets the virtual host to target.
	 * 
	 * @param host
	 *            the virtual host name that should be used instead of physical
	 *            host name when sending HTTP requests. Virtual host name can be
	 *            set to <tt> null</tt> if virtual host name is not to be used
	 * 
	 * @throws IllegalStateException
	 *             if the connection is already open
	 * 
	 * @deprecated no longer applicable
	 */

	public void setVirtualHost(String host) throws IllegalStateException {
		assertNotOpen();
	}

	/**
	 * Returns the port of the host.
	 * 
	 * If the port is -1 (or less than 0) the default port for the current
	 * protocol is returned.
	 * 
	 * @return the port.
	 */
	public int getPort() {
		if (portNumber < 0) {
			return isSecure() ? 443 : 80;
		} else {
			return portNumber;
		}
	}

	/**
	 * Sets the port to connect to.
	 * 
	 * @param port
	 *            the port to connect to
	 * 
	 * @throws IllegalStateException
	 *             if the connection is already open
	 */
	public void setPort(int port) throws IllegalStateException {
		assertNotOpen();
		portNumber = port;
	}

	/**
	 * Returns the proxy host.
	 * 
	 * @return the proxy host.
	 */
	public String getProxyHost() {
		return proxyHostName;
	}

	/**
	 * Sets the host to proxy through.
	 * 
	 * @param host
	 *            the host to proxy through.
	 * 
	 * @throws IllegalStateException
	 *             if the connection is already open
	 */
	public void setProxyHost(String host) throws IllegalStateException {
		assertNotOpen();
		proxyHostName = host;
	}

	/**
	 * Returns the port of the proxy host.
	 * 
	 * @return the proxy port.
	 */
	public int getProxyPort() {
		return proxyPortNumber;
	}

	/**
	 * Sets the port of the host to proxy through.
	 * 
	 * @param port
	 *            the port of the host to proxy through.
	 * 
	 * @throws IllegalStateException
	 *             if the connection is already open
	 */
	public void setProxyPort(int port) throws IllegalStateException {
		assertNotOpen();
		proxyPortNumber = port;
	}

	/**
	 * Returns <tt>true</tt> if the connection is established over a secure
	 * protocol.
	 * 
	 * @return <tt>true</tt> if connected over a secure protocol.
	 */
	public boolean isSecure() {
		return protocolInUse.isSecure();
	}

	/**
	 * Returns the protocol used to establish the connection.
	 * 
	 * @return The protocol
	 */
	public Protocol getProtocol() {
		return protocolInUse;
	}

	/**
	 * Sets the protocol used to establish the connection
	 * 
	 * @param protocol
	 *            The protocol to use.
	 * 
	 * @throws IllegalStateException
	 *             if the connection is already open
	 */
	public void setProtocol(Protocol protocol) {
		assertNotOpen();

		if (protocol == null) {
			throw new IllegalArgumentException("protocol is null");
		}

		protocolInUse = protocol;

	}

	/**
	 * Return the local address used when creating the connection. If
	 * <tt>null</tt>, the default address is used.
	 * 
	 * @return InetAddress the local address to be used when creating Sockets
	 */
	public InetAddress getLocalAddress() {
		return this.localAddress;
	}

	/**
	 * Set the local address used when creating the connection. If unset or
	 * <tt>null</tt>, the default address is used.
	 * 
	 * @param localAddress
	 *            the local address to use
	 */
	public void setLocalAddress(InetAddress localAddress) {
		assertNotOpen();
		this.localAddress = localAddress;
	}

	/**
	 * Tests if the connection is open.
	 * 
	 * @return <code>true</code> if the connection is open
	 */
	public boolean isOpen() {
		return isOpen;
	}

	/**
	 * Closes the connection if stale.
	 * 
	 * @return <code>true</code> if the connection was stale and therefore
	 *         closed, <code>false</code> otherwise.
	 * 
	 * @see #isStale()
	 * 
	 * @since 3.0
	 */
	public boolean closeIfStale() throws IOException {
		if (isOpen && isStale()) {
			// LOG.debug("Connection is stale, closing...");
			close();
			return true;
		}
		return false;
	}

	/**
	 * Tests if stale checking is enabled.
	 * 
	 * @return <code>true</code> if enabled
	 * 
	 * @see #isStale()
	 * 
	 * @deprecated Use {@link HttpConnectionParams#isStaleCheckingEnabled()},
	 *             {@link HttpConnection#getParams()}.
	 */
	public boolean isStaleCheckingEnabled() {
		return this.params.isStaleCheckingEnabled();
	}

	/**
	 * Sets whether or not isStale() will be called when testing if this
	 * connection is open.
	 * 
	 * <p>
	 * Setting this flag to <code>false</code> will increase performance when
	 * reusing connections, but it will also make them less reliable. Stale
	 * checking ensures that connections are viable before they are used. When
	 * set to <code>false</code> some method executions will result in
	 * IOExceptions and they will have to be retried.
	 * </p>
	 * 
	 * @param staleCheckEnabled
	 *            <code>true</code> to enable isStale()
	 * 
	 * @see #isStale()
	 * @see #isOpen()
	 * 
	 * @deprecated Use
	 *             {@link HttpConnectionParams#setStaleCheckingEnabled(boolean)}
	 *             , {@link HttpConnection#getParams()}.
	 */
	public void setStaleCheckingEnabled(boolean staleCheckEnabled) {
		this.params.setStaleCheckingEnabled(staleCheckEnabled);
	}

	/**
	 * Determines whether this connection is "stale", which is to say that
	 * either it is no longer open, or an attempt to read the connection would
	 * fail.
	 * 
	 * <p>
	 * Unfortunately, due to the limitations of the JREs prior to 1.4, it is not
	 * possible to test a connection to see if both the read and write channels
	 * are open - except by reading and writing. This leads to a difficulty when
	 * some connections leave the "write" channel open, but close the read
	 * channel and ignore the request. This function attempts to ameliorate that
	 * problem by doing a test read, assuming that the caller will be doing a
	 * write followed by a read, rather than the other way around.
	 * </p>
	 * 
	 * <p>
	 * To avoid side-effects, the underlying connection is wrapped by a
	 * {@link BufferedInputStream}, so although data might be read, what is
	 * visible to clients of the connection will not change with this call.
	 * </p
	 * .
	 * 
	 * @throws IOException
	 *             if the stale connection test is interrupted.
	 * 
	 * @return <tt>true</tt> if the connection is already closed, or a read
	 *         would fail.
	 */
	protected boolean isStale() throws IOException {
		boolean isStale = true;
		if (isOpen) {
			// the connection is open, but now we have to see if we can read it
			// assume the connection is not stale.
			isStale = false;
			try {
				if (inputStream.available() <= 0) {
					try {
						socket.setSoTimeout(1);
						inputStream.mark(1);
						int byteRead = inputStream.read();
						if (byteRead == -1) {
							// again - if the socket is reporting all data read,
							// probably stale
							isStale = true;
						} else {
							inputStream.reset();
						}
					} finally {
						socket.setSoTimeout(this.params.getSoTimeout());
					}
				}
			} catch (InterruptedIOException e) {
				if (!ExceptionUtil.isSocketTimeoutException(e)) {
					throw e;
				}
				// aha - the connection is NOT stale - continue on!
			} catch (IOException e) {
				// oops - the connection is stale, the read or soTimeout failed.
				// LOG.debug(
				// "An error occurred while reading from the socket, is appears to be stale",
				// e
				// );
				isStale = true;
			}
		}

		return isStale;
	}

	/**
	 * Returns <tt>true</tt> if the connection is established via a proxy,
	 * <tt>false</tt> otherwise.
	 * 
	 * @return <tt>true</tt> if a proxy is used to establish the connection,
	 *         <tt>false</tt> otherwise.
	 */
	public boolean isProxied() {
		return (!(null == proxyHostName || 0 >= proxyPortNumber));
	}

	/**
	 * Set the state to keep track of the last response for the last request.
	 * 
	 * <p>
	 * The connection managers use this to ensure that previous requests are
	 * properly closed before a new request is attempted. That way, a GET
	 * request need not be read in its entirety before a new request is issued.
	 * Instead, this stream can be closed as appropriate.
	 * </p>
	 * 
	 * @param inStream
	 *            The stream associated with an HttpMethod.
	 */
	public void setLastResponseInputStream(InputStream inStream) {
		lastResponseInputStream = inStream;
	}

	/**
	 * Returns the stream used to read the last response's body.
	 * 
	 * <p>
	 * Clients will generally not need to call this function unless using
	 * HttpConnection directly, instead of calling
	 * {@link HttpClient#executeMethod}. For those clients, call this function,
	 * and if it returns a non-null stream, close the stream before attempting
	 * to execute a method. Note that calling "close" on the stream returned by
	 * this function <i>may</i> close the connection if the previous response
	 * contained a "Connection: close" header.
	 * </p>
	 * 
	 * @return An {@link InputStream} corresponding to the body of the last
	 *         response.
	 */
	public InputStream getLastResponseInputStream() {
		return lastResponseInputStream;
	}

	// --------------------------------------------------- Other Public Methods

	/**
	 * Returns {@link HttpConnectionParams HTTP protocol parameters} associated
	 * with this method.
	 * 
	 * @return HTTP parameters.
	 * 
	 * @since 3.0
	 */
	public HttpConnectionParams getParams() {
		return this.params;
	}

	/**
	 * Assigns {@link HttpConnectionParams HTTP protocol parameters} for this
	 * method.
	 * 
	 * @since 3.0
	 * 
	 * @see HttpConnectionParams
	 */
	public void setParams(final HttpConnectionParams params) {
		if (params == null) {
			throw new IllegalArgumentException("Parameters may not be null");
		}
		this.params = params;
	}

	/**
	 * Set the {@link Socket}'s timeout, via {@link Socket#setSoTimeout}. If the
	 * connection is already open, the SO_TIMEOUT is changed. If no connection
	 * is open, then subsequent connections will use the timeout value.
	 * <p>
	 * Note: This is not a connection timeout but a timeout on network traffic!
	 * 
	 * @param timeout
	 *            the timeout value
	 * @throws SocketException
	 *             - if there is an error in the underlying protocol, such as a
	 *             TCP error.
	 * 
	 * @deprecated Use {@link HttpConnectionParams#setSoTimeout(int)},
	 *             {@link HttpConnection#getParams()}.
	 */
	public void setSoTimeout(int timeout) throws SocketException,
			IllegalStateException {
		this.params.setSoTimeout(timeout);
		if (this.socket != null) {
			this.socket.setSoTimeout(timeout);
		}
	}

	/**
	 * Sets <code>SO_TIMEOUT</code> value directly on the underlying
	 * {@link Socket socket}. This method does not change the default read
	 * timeout value set via {@link HttpConnectionParams}.
	 * 
	 * @param timeout
	 *            the timeout value
	 * @throws SocketException
	 *             - if there is an error in the underlying protocol, such as a
	 *             TCP error.
	 * @throws IllegalStateException
	 *             if not connected
	 * 
	 * @since 3.0
	 */
	public void setSocketTimeout(int timeout) throws SocketException,
			IllegalStateException {
		assertOpen();
		if (this.socket != null) {
			this.socket.setSoTimeout(timeout);
		}
	}

	/**
	 * Returns the {@link Socket}'s timeout, via {@link Socket#getSoTimeout}, if
	 * the connection is already open. If no connection is open, return the
	 * value subsequent connection will use.
	 * <p>
	 * Note: This is not a connection timeout but a timeout on network traffic!
	 * 
	 * @return the timeout value
	 * 
	 * @deprecated Use {@link HttpConnectionParams#getSoTimeout()},
	 *             {@link HttpConnection#getParams()}.
	 */
	public int getSoTimeout() throws SocketException {
		return this.params.getSoTimeout();
	}

	/**
	 * Sets the connection timeout. This is the maximum time that may be spent
	 * until a connection is established. The connection will fail after this
	 * amount of time.
	 * 
	 * @param timeout
	 *            The timeout in milliseconds. 0 means timeout is not used.
	 * 
	 * @deprecated Use {@link HttpConnectionParams#setConnectionTimeout(int)},
	 *             {@link HttpConnection#getParams()}.
	 */
	public void setConnectionTimeout(int timeout) {
		this.params.setConnectionTimeout(timeout);
	}

	/**
	 * Establishes a connection to the specified host and port (via a proxy if
	 * specified). The underlying socket is created from the
	 * {@link ProtocolSocketFactory}.
	 * 
	 * @throws IOException
	 *             if an attempt to establish the connection results in an I/O
	 *             error.
	 */
	public void open() throws IOException {
		// LOG.trace("enter HttpConnection.open()");

		final String host = (proxyHostName == null) ? hostName : proxyHostName;
		final int port = (proxyHostName == null) ? portNumber : proxyPortNumber;
		assertNotOpen();

		// if (LOG.isDebugEnabled()) {
		// LOG.debug("Open connection to " + host + ":" + port);
		// }

		try {
			if (this.socket == null) {
				usingSecureSocket = isSecure() && !isProxied();
				// use the protocol's socket factory unless this is a secure
				// proxied connection
				ProtocolSocketFactory socketFactory = null;
				if (isSecure() && isProxied()) {
					Protocol defaultprotocol = Protocol.getProtocol("http");
					socketFactory = defaultprotocol.getSocketFactory();
				} else {
					socketFactory = this.protocolInUse.getSocketFactory();
				}
				this.socket = socketFactory.createSocket(host, port,
						localAddress, 0, this.params);
			}

			/*
			 * "Nagling has been broadly implemented across networks, including
			 * the Internet, and is generally performed by default - although it
			 * is sometimes considered to be undesirable in highly interactive
			 * environments, such as some client/server situations. In such
			 * cases, nagling may be turned off through use of the TCP_NODELAY
			 * sockets option."
			 */

			socket.setTcpNoDelay(this.params.getTcpNoDelay());
			socket.setSoTimeout(this.params.getSoTimeout());

			int linger = this.params.getLinger();
			if (linger >= 0) {
				socket.setSoLinger(linger > 0, linger);
			}

			int sndBufSize = this.params.getSendBufferSize();
			if (sndBufSize >= 0) {
				socket.setSendBufferSize(sndBufSize);
			}
			int rcvBufSize = this.params.getReceiveBufferSize();
			if (rcvBufSize >= 0) {
				socket.setReceiveBufferSize(rcvBufSize);
			}
			int outbuffersize = socket.getSendBufferSize();
			if ((outbuffersize > 2048) || (outbuffersize <= 0)) {
				outbuffersize = 2048;
			}
			int inbuffersize = socket.getReceiveBufferSize();
			if ((inbuffersize > 2048) || (inbuffersize <= 0)) {
				inbuffersize = 2048;
			}
			inputStream = new BufferedInputStream(socket.getInputStream(),
					inbuffersize);
			outputStream = new BufferedOutputStream(socket.getOutputStream(),
					outbuffersize);
			isOpen = true;
		} catch (IOException e) {
			// Connection wasn't opened properly
			// so close everything out
			closeSocketAndStreams();
			throw e;
		}
	}

	/**
	 * Instructs the proxy to establish a secure tunnel to the host. The socket
	 * will be switched to the secure socket. Subsequent communication is done
	 * via the secure socket. The method can only be called once on a proxied
	 * secure connection.
	 * 
	 * @throws IllegalStateException
	 *             if connection is not secure and proxied or if the socket is
	 *             already secure.
	 * @throws IOException
	 *             if an attempt to establish the secure tunnel results in an
	 *             I/O error.
	 */
	public void tunnelCreated() throws IllegalStateException, IOException {
		// LOG.trace("enter HttpConnection.tunnelCreated()");

		if (!isSecure() || !isProxied()) {
			throw new IllegalStateException("Connection must be secure "
					+ "and proxied to use this feature");
		}

		if (usingSecureSocket) {
			throw new IllegalStateException("Already using a secure socket");
		}

		// if (LOG.isDebugEnabled()) {
		// LOG.debug("Secure tunnel to " + this.hostName + ":" +
		// this.portNumber);
		// }

		SecureProtocolSocketFactory socketFactory = (SecureProtocolSocketFactory) protocolInUse
				.getSocketFactory();

		socket = socketFactory.createSocket(socket, hostName, portNumber, true);
		int sndBufSize = this.params.getSendBufferSize();
		if (sndBufSize >= 0) {
			socket.setSendBufferSize(sndBufSize);
		}
		int rcvBufSize = this.params.getReceiveBufferSize();
		if (rcvBufSize >= 0) {
			socket.setReceiveBufferSize(rcvBufSize);
		}
		int outbuffersize = socket.getSendBufferSize();
		if (outbuffersize > 2048) {
			outbuffersize = 2048;
		}
		int inbuffersize = socket.getReceiveBufferSize();
		if (inbuffersize > 2048) {
			inbuffersize = 2048;
		}
		inputStream = new BufferedInputStream(socket.getInputStream(),
				inbuffersize);
		outputStream = new BufferedOutputStream(socket.getOutputStream(),
				outbuffersize);
		usingSecureSocket = true;
		tunnelEstablished = true;
	}

	/**
	 * Indicates if the connection is completely transparent from end to end.
	 * 
	 * @return true if conncetion is not proxied or tunneled through a
	 *         transparent proxy; false otherwise.
	 */
	public boolean isTransparent() {
		return !isProxied() || tunnelEstablished;
	}

	/**
	 * Flushes the output request stream. This method should be called to ensure
	 * that data written to the request OutputStream is sent to the server.
	 * 
	 * @throws IOException
	 *             if an I/O problem occurs
	 */
	public void flushRequestOutputStream() throws IOException {
		// LOG.trace("enter HttpConnection.flushRequestOutputStream()");
		assertOpen();
		outputStream.flush();
	}

	/**
	 * Returns an {@link OutputStream} suitable for writing the request.
	 * 
	 * @throws IllegalStateException
	 *             if the connection is not open
	 * @throws IOException
	 *             if an I/O problem occurs
	 * @return a stream to write the request to
	 */
	public OutputStream getRequestOutputStream() throws IOException,
			IllegalStateException {
		// LOG.trace("enter HttpConnection.getRequestOutputStream()");
		assertOpen();
		OutputStream out = this.outputStream;
		// if (Wire.CONTENT_WIRE.enabled()) {
		// out = new WireLogOutputStream(out, Wire.CONTENT_WIRE);
		// }
		return out;
	}

	/**
	 * Return a {@link InputStream} suitable for reading the response.
	 * 
	 * @return InputStream The response input stream.
	 * @throws IOException
	 *             If an IO problem occurs
	 * @throws IllegalStateException
	 *             If the connection isn't open.
	 */
	public InputStream getResponseInputStream() throws IOException,
			IllegalStateException {
		// LOG.trace("enter HttpConnection.getResponseInputStream()");
		assertOpen();
		return inputStream;
	}

	/**
	 * Tests if input data avaialble. This method returns immediately and does
	 * not perform any read operations on the input socket
	 * 
	 * @return boolean <tt>true</tt> if input data is available, <tt>false</tt>
	 *         otherwise.
	 * 
	 * @throws IOException
	 *             If an IO problem occurs
	 * @throws IllegalStateException
	 *             If the connection isn't open.
	 */
	public boolean isResponseAvailable() throws IOException {
		// LOG.trace("enter HttpConnection.isResponseAvailable()");
		if (this.isOpen) {
			return this.inputStream.available() > 0;
		} else {
			return false;
		}
	}

	/**
	 * Tests if input data becomes available within the given period time in
	 * milliseconds.
	 * 
	 * @param timeout
	 *            The number milliseconds to wait for input data to become
	 *            available
	 * @return boolean <tt>true</tt> if input data is availble, <tt>false</tt>
	 *         otherwise.
	 * 
	 * @throws IOException
	 *             If an IO problem occurs
	 * @throws IllegalStateException
	 *             If the connection isn't open.
	 */
	public boolean isResponseAvailable(int timeout) throws IOException {
		// LOG.trace("enter HttpConnection.isResponseAvailable(int)");
		assertOpen();
		boolean result = false;
		if (this.inputStream.available() > 0) {
			result = true;
		} else {
			try {
				this.socket.setSoTimeout(timeout);
				inputStream.mark(1);
				int byteRead = inputStream.read();
				if (byteRead != -1) {
					inputStream.reset();
					// LOG.debug("Input data available");
					result = true;
				} else {
					// LOG.debug("Input data not available");
				}
			} catch (InterruptedIOException e) {
				if (!ExceptionUtil.isSocketTimeoutException(e)) {
					throw e;
				}
				// if (LOG.isDebugEnabled()) {
				// LOG.debug("Input data not available after " + timeout +
				// " ms");
				// }
			} finally {
				try {
					socket.setSoTimeout(this.params.getSoTimeout());
				} catch (IOException ioe) {
					// LOG.debug("An error ocurred while resetting soTimeout, we will assume that"
					// + " no response is available.",
					// ioe);
					result = false;
				}
			}
		}
		return result;
	}

	/**
	 * Writes the specified bytes to the output stream.
	 * 
	 * @param data
	 *            the data to be written
	 * @throws IllegalStateException
	 *             if not connected
	 * @throws IOException
	 *             if an I/O problem occurs
	 * @see #write(byte[],int,int)
	 */
	public void write(byte[] data) throws IOException, IllegalStateException {
		// LOG.trace("enter HttpConnection.write(byte[])");
		this.write(data, 0, data.length);
	}

	/**
	 * Writes <i>length</i> bytes in <i>data</i> starting at <i>offset</i> to
	 * the output stream.
	 * 
	 * The general contract for write(b, off, len) is that some of the bytes in
	 * the array b are written to the output stream in order; element b[off] is
	 * the first byte written and b[off+len-1] is the last byte written by this
	 * operation.
	 * 
	 * @param data
	 *            array containing the data to be written.
	 * @param offset
	 *            the start offset in the data.
	 * @param length
	 *            the number of bytes to write.
	 * @throws IllegalStateException
	 *             if not connected
	 * @throws IOException
	 *             if an I/O problem occurs
	 */
	public void write(byte[] data, int offset, int length) throws IOException,
			IllegalStateException {
		// LOG.trace("enter HttpConnection.write(byte[], int, int)");

		if (offset < 0) {
			throw new IllegalArgumentException(
					"Array offset may not be negative");
		}
		if (length < 0) {
			throw new IllegalArgumentException(
					"Array length may not be negative");
		}
		if (offset + length > data.length) {
			throw new IllegalArgumentException(
					"Given offset and length exceed the array length");
		}
		assertOpen();
		this.outputStream.write(data, offset, length);
	}

	/**
	 * Writes the specified bytes, followed by <tt>"\r\n".getBytes()</tt> to the
	 * output stream.
	 * 
	 * @param data
	 *            the bytes to be written
	 * @throws IllegalStateException
	 *             if the connection is not open
	 * @throws IOException
	 *             if an I/O problem occurs
	 */
	public void writeLine(byte[] data) throws IOException,
			IllegalStateException {
		// LOG.trace("enter HttpConnection.writeLine(byte[])");
		write(data);
		writeLine();
	}

	/**
	 * Writes <tt>"\r\n".getBytes()</tt> to the output stream.
	 * 
	 * @throws IllegalStateException
	 *             if the connection is not open
	 * @throws IOException
	 *             if an I/O problem occurs
	 */
	public void writeLine() throws IOException, IllegalStateException {
		// LOG.trace("enter HttpConnection.writeLine()");
		write(CRLF);
	}

	/**
	 * @deprecated Use {@link #print(String, String)}
	 * 
	 *             Writes the specified String (as bytes) to the output stream.
	 * 
	 * @param data
	 *            the string to be written
	 * @throws IllegalStateException
	 *             if the connection is not open
	 * @throws IOException
	 *             if an I/O problem occurs
	 */
	public void print(String data) throws IOException, IllegalStateException {
		// LOG.trace("enter HttpConnection.print(String)");
		write(EncodingUtil.getBytes(data, "ISO-8859-1"));
	}

	/**
	 * Writes the specified String (as bytes) to the output stream.
	 * 
	 * @param data
	 *            the string to be written
	 * @param charset
	 *            the charset to use for writing the data
	 * @throws IllegalStateException
	 *             if the connection is not open
	 * @throws IOException
	 *             if an I/O problem occurs
	 * 
	 * @since 3.0
	 */
	public void print(String data, String charset) throws IOException,
			IllegalStateException {
		// LOG.trace("enter HttpConnection.print(String)");
		write(EncodingUtil.getBytes(data, charset));
	}

	/**
	 * @deprecated Use {@link #printLine(String, String)}
	 * 
	 *             Writes the specified String (as bytes), followed by
	 *             <tt>"\r\n".getBytes()</tt> to the output stream.
	 * 
	 * @param data
	 *            the data to be written
	 * @throws IllegalStateException
	 *             if the connection is not open
	 * @throws IOException
	 *             if an I/O problem occurs
	 */
	public void printLine(String data) throws IOException,
			IllegalStateException {
		// LOG.trace("enter HttpConnection.printLine(String)");
		writeLine(EncodingUtil.getBytes(data, "ISO-8859-1"));
	}

	/**
	 * Writes the specified String (as bytes), followed by
	 * <tt>"\r\n".getBytes()</tt> to the output stream.
	 * 
	 * @param data
	 *            the data to be written
	 * @param charset
	 *            the charset to use for writing the data
	 * @throws IllegalStateException
	 *             if the connection is not open
	 * @throws IOException
	 *             if an I/O problem occurs
	 * 
	 * @since 3.0
	 */
	public void printLine(String data, String charset) throws IOException,
			IllegalStateException {
		// LOG.trace("enter HttpConnection.printLine(String)");
		writeLine(EncodingUtil.getBytes(data, charset));
	}

	/**
	 * Writes <tt>"\r\n".getBytes()</tt> to the output stream.
	 * 
	 * @throws IllegalStateException
	 *             if the connection is not open
	 * @throws IOException
	 *             if an I/O problem occurs
	 */
	public void printLine() throws IOException, IllegalStateException {
		// LOG.trace("enter HttpConnection.printLine()");
		writeLine();
	}

	/**
	 * Reads up to <tt>"\n"</tt> from the (unchunked) input stream. If the
	 * stream ends before the line terminator is found, the last part of the
	 * string will still be returned.
	 * 
	 * @throws IllegalStateException
	 *             if the connection is not open
	 * @throws IOException
	 *             if an I/O problem occurs
	 * @return a line from the response
	 * 
	 * @deprecated use #readLine(String)
	 */
	public String readLine() throws IOException, IllegalStateException {
		// LOG.trace("enter HttpConnection.readLine()");

		assertOpen();
		return HttpParser.readLine(inputStream);
	}

	/**
	 * Reads up to <tt>"\n"</tt> from the (unchunked) input stream. If the
	 * stream ends before the line terminator is found, the last part of the
	 * string will still be returned.
	 * 
	 * @param charset
	 *            the charset to use for reading the data
	 * 
	 * @throws IllegalStateException
	 *             if the connection is not open
	 * @throws IOException
	 *             if an I/O problem occurs
	 * @return a line from the response
	 * 
	 * @since 3.0
	 */
	public String readLine(final String charset) throws IOException,
			IllegalStateException {
		// LOG.trace("enter HttpConnection.readLine()");

		assertOpen();
		return HttpParser.readLine(inputStream, charset);
	}

	/**
	 * Attempts to shutdown the {@link Socket}'s output, via
	 * Socket.shutdownOutput() when running on JVM 1.3 or higher.
	 * 
	 * @deprecated unused
	 */
	public void shutdownOutput() {
		// LOG.trace("enter HttpConnection.shutdownOutput()");

		try {
			// Socket.shutdownOutput is a JDK 1.3
			// method. We'll use reflection in case
			// we're running in an older VM
			Class[] paramsClasses = new Class[0];
			Method shutdownOutput = socket.getClass().getMethod(
					"shutdownOutput", paramsClasses);
			Object[] params = new Object[0];
			shutdownOutput.invoke(socket, params);
		} catch (Exception ex) {
			// LOG.debug("Unexpected Exception caught", ex);
			// Ignore, and hope everything goes right
		}
		// close output stream?
	}

	/**
	 * Closes the socket and streams.
	 */
	public void close() {
		// LOG.trace("enter HttpConnection.close()");
		closeSocketAndStreams();
	}

	/**
	 * Returns the httpConnectionManager.
	 * 
	 * @return HttpConnectionManager
	 */
	public HttpConnectionManager getHttpConnectionManager() {
		return httpConnectionManager;
	}

	/**
	 * Sets the httpConnectionManager.
	 * 
	 * @param httpConnectionManager
	 *            The httpConnectionManager to set
	 */
	public void setHttpConnectionManager(
			HttpConnectionManager httpConnectionManager) {
		this.httpConnectionManager = httpConnectionManager;
	}

	/**
	 * Releases the connection. If the connection is locked or does not have a
	 * connection manager associated with it, this method has no effect. Note
	 * that it is completely safe to call this method multiple times.
	 */
	public void releaseConnection() {
		// LOG.trace("enter HttpConnection.releaseConnection()");
		if (locked) {
			// LOG.debug("Connection is locked.  Call to releaseConnection() ignored.");
		} else if (httpConnectionManager != null) {
			// LOG.debug("Releasing connection back to connection manager.");
			httpConnectionManager.releaseConnection(this);
		} else {
			// LOG.warn("HttpConnectionManager is null.  Connection cannot be released.");
		}
	}

	/**
	 * Tests if the connection is locked. Locked connections cannot be released.
	 * An attempt to release a locked connection will have no effect.
	 * 
	 * @return <tt>true</tt> if the connection is locked, <tt>false</tt>
	 *         otherwise.
	 * 
	 * @since 3.0
	 */
	protected boolean isLocked() {
		return locked;
	}

	/**
	 * Locks or unlocks the connection. Locked connections cannot be released.
	 * An attempt to release a locked connection will have no effect.
	 * 
	 * @param locked
	 *            <tt>true</tt> to lock the connection, <tt>false</tt> to unlock
	 *            the connection.
	 * 
	 * @since 3.0
	 */
	protected void setLocked(boolean locked) {
		this.locked = locked;
	}

	// ------------------------------------------------------ Protected Methods

	/**
	 * Closes everything out.
	 */
	protected void closeSocketAndStreams() {
		// LOG.trace("enter HttpConnection.closeSockedAndStreams()");

		isOpen = false;

		// no longer care about previous responses...
		lastResponseInputStream = null;

		if (null != outputStream) {
			OutputStream temp = outputStream;
			outputStream = null;
			try {
				temp.close();
			} catch (Exception ex) {
				// LOG.debug("Exception caught when closing output", ex);
				// ignored
			}
		}

		if (null != inputStream) {
			InputStream temp = inputStream;
			inputStream = null;
			try {
				temp.close();
			} catch (Exception ex) {
				// LOG.debug("Exception caught when closing input", ex);
				// ignored
			}
		}

		if (null != socket) {
			Socket temp = socket;
			socket = null;
			try {
				temp.close();
			} catch (Exception ex) {
				// LOG.debug("Exception caught when closing socket", ex);
				// ignored
			}
		}

		tunnelEstablished = false;
		usingSecureSocket = false;
	}

	/**
	 * Throws an {@link IllegalStateException} if the connection is already
	 * open.
	 * 
	 * @throws IllegalStateException
	 *             if connected
	 */
	protected void assertNotOpen() throws IllegalStateException {
		if (isOpen) {
			throw new IllegalStateException("Connection is open");
		}
	}

	/**
	 * Throws an {@link IllegalStateException} if the connection is not open.
	 * 
	 * @throws IllegalStateException
	 *             if not connected
	 */
	protected void assertOpen() throws IllegalStateException {
		if (!isOpen) {
			throw new IllegalStateException("Connection is not open");
		}
	}

	/**
	 * Gets the socket's sendBufferSize.
	 * 
	 * @return the size of the buffer for the socket OutputStream, -1 if the
	 *         value has not been set and the socket has not been opened
	 * 
	 * @throws SocketException
	 *             if an error occurs while getting the socket value
	 * 
	 * @see Socket#getSendBufferSize()
	 */
	public int getSendBufferSize() throws SocketException {
		if (socket == null) {
			return -1;
		} else {
			return socket.getSendBufferSize();
		}
	}

	/**
	 * Sets the socket's sendBufferSize.
	 * 
	 * @param sendBufferSize
	 *            the size to set for the socket OutputStream
	 * 
	 * @throws SocketException
	 *             if an error occurs while setting the socket value
	 * 
	 * @see Socket#setSendBufferSize(int)
	 * 
	 * @deprecated Use {@link HttpConnectionParams#setSendBufferSize(int)},
	 *             {@link HttpConnection#getParams()}.
	 */
	public void setSendBufferSize(int sendBufferSize) throws SocketException {
		this.params.setSendBufferSize(sendBufferSize);
	}

	// ------------------------------------------------------- Static Variable

	/** <tt>"\r\n"</tt>, as bytes. */
	private static final byte[] CRLF = new byte[] { (byte) 13, (byte) 10 };

	/** Log object for this class. */
	// private static final Log LOG = LogFactory.getLog(HttpConnection.class);

	// ----------------------------------------------------- Instance Variables

	/** My host. */
	private String hostName = null;

	/** My port. */
	private int portNumber = -1;

	/** My proxy host. */
	private String proxyHostName = null;

	/** My proxy port. */
	private int proxyPortNumber = -1;

	/** My client Socket. */
	private Socket socket = null;

	/** My InputStream. */
	private InputStream inputStream = null;

	/** My OutputStream. */
	private OutputStream outputStream = null;

	/** An {@link InputStream} for the response to an individual request. */
	private InputStream lastResponseInputStream = null;

	/** Whether or not the connection is connected. */
	protected boolean isOpen = false;

	/** the protocol being used */
	private Protocol protocolInUse;

	/** Collection of HTTP parameters associated with this HTTP connection */
	private HttpConnectionParams params = new HttpConnectionParams();

	/**
	 * flag to indicate if this connection can be released, if locked the
	 * connection cannot be released
	 */
	private boolean locked = false;

	/** Whether or not the socket is a secure one. */
	private boolean usingSecureSocket = false;

	/** Whether the connection is open via a secure tunnel or not */
	private boolean tunnelEstablished = false;

	/** the connection manager that created this connection or null */
	private HttpConnectionManager httpConnectionManager;

	/**
	 * The local interface on which the connection is created, or null for the
	 * default
	 */
	private InetAddress localAddress;
}
